/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

// not finished implemented

package stanford.tagger;

import edu.stanford.nlp.util.StringUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *
 * @author hadoop
 */
public class ExtractorFramesRareGeneral {

    /**
   * Last 1-4 characters of word
   */
  private static final Extractor cWordSuff1 = new ExtractorCWordSuff(1);
  private static final Extractor cWordSuff2 = new ExtractorCWordSuff(2);
  private static final Extractor cWordSuff3 = new ExtractorCWordSuff(3);
  private static final Extractor cWordSuff4 = new ExtractorCWordSuff(4);

  /**
   * "1" iff word contains 1 or more upper case characters (somewhere)
   */
  private static final Extractor cWordUppCase = new ExtractorUCase();

  /**
   * "1" iff word contains 1 or more digit characters (somewhere)
   */
  private static final Extractor cWordNumber = new ExtractorCNumber();

  /**
   * "1" iff word contains 1 or more dash characters (somewhere)
   */
  private static final Extractor cWordDash = new ExtractorDash();

  /**
   * "1" if token has no lower case letters
   */
  private static final Extractor cNoLower = new ExtractorAllCap();

  /**
   * "1" if token has only upper case letters
   */
  private static final Extractor cAllCapitalized = new ExtractorAllCapitalized();

  /**
   * "1" if capitalized and one of following 3 words is Inc., Co., or Corp.
   */
  private static final Extractor cCompany = new CompanyNameDetector();

  /**
   * "1" if word contains letter, digit, and dash, in any position and case
   */
  private static final Extractor cLetterDigitDash = new ExtractorLetterDigitDash();

  /**
   * "1" if word contains uppercase letter, digit, and dash
   */
  private static final Extractor cUpperDigitDash = new ExtractorUpperDigitDash();

  /**
   * Distance to lowercase word.  Used by another extractor....
   */
  private static final Extractor cCapDist = new ExtractorCapDistLC();


  private static final Extractor[] eFrames_motley_naacl2003 = { cWordUppCase, cWordNumber, cWordDash, cNoLower, cLetterDigitDash, cCompany, cAllCapitalized, cUpperDigitDash};
  private static final Extractor[] eFrames_motley_naacl2003_left = { cWordUppCase, cWordNumber, cWordDash, cNoLower, cLetterDigitDash, cAllCapitalized, cUpperDigitDash};


    /** Get an array of rare word feature Extractor identified by a name.
   *  Note: Names used here must also be known in getExtractorFrames, so we
   *  can appropriately add error messages.  So if you add a keyword here,
   *  add it there as one to be ignored, too. (In the next iteration, this
   *  class and ExtractorFrames should probably just be combined).
   *
   *  @param identifier Describes a set of extractors for rare word features
   *  @return A set of extractors for rare word features
   */
  protected static Extractor[] getExtractorFramesRare(String identifier, TTags ttags) {
    ArrayList<Extractor> extrs = new ArrayList<Extractor>();
    List<String> args = StringUtils.valueSplit(identifier, "[a-z0-9]*(?:\\([^)]*\\))?", "\\s*,\\s*");

    for (String arg : args) {
      if ("naacl2003unknowns".equalsIgnoreCase(arg)) {
          System.err.println("naacl2003unknowns");
//        extrs.addAll(Arrays.asList(eFrames_motley_naacl2003));
//        getNaaclExtractors(extrs);
      } else if (("lnaacl2003unknowns").equalsIgnoreCase(arg)) {
          System.err.println("lnaacl2003unknowns");
//        extrs.addAll(Arrays.asList(eFrames_motley_naacl2003_left));
//        getNaaclExtractors(extrs);
        // TODO: test this next one
      } else if ("naacl2003conjunctions".equalsIgnoreCase(arg)) {
          System.err.println("naacl2003conjunctions");
//        extrs.addAll(Arrays.asList(naacl2003Conjunctions()));
      } else if (arg.startsWith("wordshapes(")) {
          System.err.println("wordshapes");
//        int lWindow = Extractor.getParenthesizedNum(arg, 1);
//        int rWindow = Extractor.getParenthesizedNum(arg, 2);
//        for (int i = lWindow; i <= rWindow; i++) {
//          extrs.add(new ExtractorWordShapeClassifier(i, "chris2"));
//        }
      } else if (arg.startsWith("unicodeshapes(")) {
          System.err.println("unicodeshapes");
//        int lWindow = Extractor.getParenthesizedNum(arg, 1);
//        int rWindow = Extractor.getParenthesizedNum(arg, 2);
//        for (int i = lWindow; i <= rWindow; i++) {
//          extrs.add(new ExtractorWordShapeClassifier(i, "chris4"));
//        }
      } else if (arg.startsWith("unicodeshapeconjunction(")) {
          System.err.println("unicodeshapeconjunction");
//        int lWindow = Extractor.getParenthesizedNum(arg, 1);
//        int rWindow = Extractor.getParenthesizedNum(arg, 2);
//        extrs.add(new ExtractorWordShapeConjunction(lWindow, rWindow, "chris4"));
      } else if ("sighan2005".equalsIgnoreCase(arg)) {
          System.err.println("sighan2005");
//        extrs.add(cWordNumber);
//        for (int i = 1; i <= 4; i++) {
//          extrs.add(new ExtractorCWordSuff(i));
//        }
//        for (int i = 1; i <= 4; i++) {
//          extrs.add(new ExtractorCWordPref(i));
//        }
      } else if (arg.startsWith("chinesedictionaryfeatures(")) {
          System.err.println("chinesedictionaryfeatures");
//        throw new RuntimeException("These features are no longer supported." +
//                                   "  The paths and data files associated " +
//                                   "with this material are out of date, and " +
//                                   "the classes used are not thread-safe.  " +
//                                   "Those problems would need to be fixed " +
//                                   "to use this feature.");
        
      } else if (arg.equalsIgnoreCase("motleyUnknown")) {  // This is naacl2003unknown minus prefix and suffix features.
            System.err.println("motleyUnknown");
//        extrs.addAll(Arrays.asList(eFrames_motley_naacl2003));
      } else if (arg.startsWith("suffix(")) {
          System.err.println("suffix");
//        int max = Extractor.getParenthesizedNum(arg, 1);
//        for (int i = 1; i <= max; i++) {
//          extrs.add(new ExtractorCWordSuff(i));
//        }
      } else if (arg.startsWith("prefix(")) {
          System.err.println("prefix");
//        int max = Extractor.getParenthesizedNum(arg, 1);
//        for (int i = 1; i <= max; i++) {
//          extrs.add(new ExtractorCWordPref(i));
//        }
      } else if (arg.startsWith("prefixsuffix(")) {
          System.err.println("prefixsuffix");
//        int max = Extractor.getParenthesizedNum(arg, 1);
//        for (int i = 1; i <= max; i++) {
//          extrs.add(new ExtractorsConjunction(new ExtractorCWordPref(i), new ExtractorCWordSuff(i)));
//        }
      } else if (arg.startsWith("capitalizationsuffix(")) {
          System.err.println("capitalizationsuffix");
//        int max = Extractor.getParenthesizedNum(arg, 1);
//        for (int i = 1; i <= max; i++) {
//          extrs.add(new ExtractorsConjunction(cWordUppCase, new ExtractorCWordSuff(i)));
//        }
      } else if (arg.startsWith("distsim(")) {
          System.err.println("distsim");
//        String path = Extractor.getParenthesizedArg(arg, 1);
//        // traditional nlp filesystem location is: /u/nlp/data/pos_tags_are_useless/egw.bnc.200.pruned
//        int lWindow = Extractor.getParenthesizedNum(arg, 2);
//        int rWindow = Extractor.getParenthesizedNum(arg, 3);
//        for (int i = lWindow; i <= rWindow; i++) {
//          extrs.add(new ExtractorDistsim(path, i));
//        }
      } else if (arg.startsWith("distsimconjunction(")) {
          System.err.println("distsimconjunction");
//        String path = Extractor.getParenthesizedArg(arg, 1);
//        int lWindow = Extractor.getParenthesizedNum(arg, 2);
//        int rWindow = Extractor.getParenthesizedNum(arg, 3);
//        extrs.add(new ExtractorDistsim.ExtractorDistsimConjunction(path, lWindow, rWindow));
      } else if (arg.equalsIgnoreCase("lctagfeatures")) {
          System.err.println("lctagfeatures");
//        extrs.addAll(Arrays.asList(lcTagFeatures(ttags)));
      }
    }

    return extrs.toArray(new Extractor[extrs.size()]);
  }

}

 /**
 * Superclass for rare word feature frames.  Provides some common functions.
 * Designed to be extended.
 */
class RareExtractor extends Extractor {

  static final String naTag = "NA";

  RareExtractor() {
    super();
  }

  RareExtractor(int position) {
    super(position, false);
  }

  static boolean startsUpperCase(String s) {
    if (s == null || s.length() == 0) {
      return false;
    }
    char ch = s.charAt(0);
    return Character.isUpperCase(ch);
  }

  /**
   * a string is lowercase if it starts with a lowercase letter
   * such as one from a to z.
   * Should we include numbers?
   * @param s The String to check
   * @return If its first character is lower case
   */
  protected static boolean startsLowerCase(String s) {
    if (s == null) {
      return false;
    }
    char ch = s.charAt(0);
    return Character.isLowerCase(ch);
  }

  protected static boolean containsDash(String s) {
    return s != null && s.indexOf('-') >= 0;
  }

  protected static boolean containsNumber(String s) {
    if (s == null) {
      return false;
    }
    for (int i = 0, len = s.length(); i < len; i++) {
      if (Character.isDigit(s.charAt(i))) {
        return true;
      }
    }
    return false;
  }

  protected static boolean containsLetter(String s) {
    if (s == null) {
      return false;
    }
    for (int i = 0, len = s.length(); i < len; i++) {
      if (Character.isLetter(s.charAt(i))) {
        return true;
      }
    }
    return false;
  }

  protected static boolean containsUpperCase(String s) {
    if (s == null) {
      return false;
    }
    for (int i = 0, len = s.length(); i < len; i++) {
      if (Character.isUpperCase(s.charAt(i))) {
        return true;
      }
    }
    return false;
  }

  protected static boolean allUpperCase(String s) {
    if (s == null) {
      return false;
    }
    for (int i = 0, len = s.length(); i < len; i++) {
      if (!Character.isUpperCase(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  static boolean noneLowerCase(String s) {
    if (s == null) {
      return false;
    }
    for (int i = 0, len = s.length(); i < len; i++) {
      if (Character.isLowerCase(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  private static final long serialVersionUID = -7682607870855426599L;

} // end class RareExtractor


/** English-specific crude company name NER. */
class CompanyNameDetector extends RareExtractor {

  private final Set<String> companyNameEnds;

  public CompanyNameDetector() {
    companyNameEnds = new HashSet<String>();
    companyNameEnds.add("Company");
    companyNameEnds.add("COMPANY");
    companyNameEnds.add("Co.");
    companyNameEnds.add("Co");  // at end of sentence in PTB
    companyNameEnds.add("Cos.");
    companyNameEnds.add("CO.");
    companyNameEnds.add("COS.");
    companyNameEnds.add("Corporation");
    companyNameEnds.add("CORPORATION");
    companyNameEnds.add("Corp.");
    companyNameEnds.add("Corp"); // at end of sentence in PTB
    companyNameEnds.add("CORP.");
    companyNameEnds.add("Incorporated");
    companyNameEnds.add("INCORPORATED");
    companyNameEnds.add("Inc.");
    companyNameEnds.add("Inc"); // at end of sentence in PTB
    companyNameEnds.add("INC.");
    companyNameEnds.add("Association");
    companyNameEnds.add("ASSOCIATION");
    companyNameEnds.add("Assn");
    companyNameEnds.add("ASSN");
    companyNameEnds.add("Limited");
    companyNameEnds.add("LIMITED");
    companyNameEnds.add("Ltd.");
    companyNameEnds.add("LTD.");
    companyNameEnds.add("L.P.");
    // companyNameEnds.add("PLC"); // Other thing added at same time.
  }

  private boolean companyNameEnd(String s) {
    return companyNameEnds.contains(s);
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if ( ! startsUpperCase(s)) {
      return "0";
    }
    for (int i = 0; i <= 3; i++) {
      String s1 = pH.getWord(h, i);
      if (companyNameEnd(s1)) {
        return "1";
      }
    }
    return "0";
  }

  @Override public boolean isLocal() { return false; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 21L;

} // end class CompanyNameDetector


class ExtractorUCase extends RareExtractor {

  public ExtractorUCase() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (containsUpperCase(s)) {
      return "1";
    }
    return "0";
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 22L;

}


class ExtractorLetterDigitDash extends RareExtractor {

  public ExtractorLetterDigitDash() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (containsLetter(s) && containsDash(s) && containsNumber(s)) {
      return "1";
    }
    return "0";
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 23;

}


class ExtractorUpperDigitDash extends RareExtractor {

  public ExtractorUpperDigitDash() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (containsUpperCase(s) && containsDash(s) && containsNumber(s)) {
      return "1";
    }
    return "0";
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 33L;

}


/** This requires the 3 character classes in order.  This was worse than ExtractorLetterDigitDash (Oct 2009) */
class ExtractorLetterDashDigit extends RareExtractor {

  public ExtractorLetterDashDigit() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (s == null) return "0";
    boolean seenLetter = false;
    boolean seenDash = false;
    boolean seenNumber = false;
    for (int i = 0, len = s.length(); i < len; i++) {
      char ch = s.charAt(i);
      if (Character.isLetter(ch)) {
        seenLetter = true;
      } else if (seenLetter && ch == '-') {
        seenDash = true;
      } else if (seenDash && Character.isDigit(ch)) {
        seenNumber = true;
        break;
      }
    }
    if (seenNumber) {
      return "1";
    }
    return "0";
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 33L;

}


/**
 * creates features which are true if the current word is all caps
 * and the distance to the first lowercase word to the left is dist
 * the distance is 1 for adjacent, 2 for one across, 3 for ... and so on.
 * inifinity if no capitalized word (we hit the start of sentence or '')
 */
class ExtractorCapDistLC extends RareExtractor {

  boolean verbose = false;

  public ExtractorCapDistLC() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String word = pH.getWord(h, 0);
    String ret;
    if (!startsUpperCase(word)) {
      if (verbose) {
        System.out.println("did not apply because not start with upper case");
      }
      return "0";
    }
    if (allUpperCase(word)) {
      ret = "all:";
    } else {
      ret = "start";
    }

    //now find the distance
    int current = -1;
    int distance = 1;

    while (true) {
      String prevWord = pH.getWord(h, current);
      if (startsLowerCase(prevWord)) {
        if (verbose) {
          System.out.println("returning " + (ret + current) + "for " + word + ' ' + prevWord);
        }
        return ret + distance;
      }
      if (prevWord.equals(naTag) || prevWord.equals("``")) {
        if (verbose) {
          System.out.println("returning " + ret + "infinity for " + word + ' ' + prevWord);
        }
        return ret + "infinity";
      }
      current--;
      distance++;
    }
  }

  @Override public boolean isDynamic() { return false; }
  @Override public boolean isLocal() { return false; }

  private static final long serialVersionUID = 34L;

}


/**
 * This feature applies when the word is capitalized
 * and the previous lower case is infinity
 * and the lower cased version of it has occured 2 or more times with tag t
 * false if the word was not seen.
 * create features only for tags that are the same as the tag t
 */
class ExtractorCapLCSeen extends RareExtractor {

  final String tag;
  int cutoff = 1;
  private final Extractor cCapDist = new ExtractorCapDistLC();

  private transient Dictionary dict;

  ExtractorCapLCSeen(String tag) {
    this.tag = tag;
  }
  
  protected void setGlobalHolder(StanfordTagger tagger) {
    this.dict = tagger.dict;
  }


  @Override
  public boolean precondition(String tag1) {
    return tag.equals(tag1);
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String res = cCapDist.extract(h, pH);
    if (res.equals("0")) {
      return res;
    }
    //otherwise it is capitalized
    String word = ExtractorFramesGeneral.cWord.extract(h, pH);
    if (dict.getCount(word, tag) > cutoff) {
      return res + tag;
    } else {
      return "0";
    }
  }

  @Override public boolean isLocal() { return false; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 35L;

}


/**
 * "1" if not first word of sentence and _some_ letter is uppercase
 */
class ExtractorMidSentenceCap extends RareExtractor {

  public ExtractorMidSentenceCap() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String prevTag = pH.getTag(h, -1);
    if(prevTag == null) { return "0"; }
    if (prevTag.equals(naTag)) {
      return "0";
    }
    String s = pH.getWord(h, 0);
    if (containsUpperCase(s)) {
      return "1";
    }
    return "0";
  }

  private static final long serialVersionUID = 24L;

  @Override public boolean isLocal() { return false; }
  @Override public boolean isDynamic() { return true; }
}


/**
 * "0" if not 1st word of sentence or not upper case, or lowercased version
 * not in dictionary.  Else first tag of word lowercased.
 */
class ExtractorStartSentenceCap extends RareExtractor {

  private transient Dictionary dict;

  public ExtractorStartSentenceCap() {
  }
  
  protected void setGlobalHolder(StanfordTagger tagger) {
    this.dict = tagger.dict;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String prevTag = pH.getTag(h, -1);
    if(prevTag == null) { return zeroSt; }
    if (!prevTag.equals(naTag)) {
      return zeroSt;
    }
    String s = pH.getWord(h, 0);
    if (startsUpperCase(s)) {
      String s1 = s.toLowerCase();
      if (dict.isUnknown(s1)) {
        return zeroSt;
      }
      return dict.getFirstTag(s1);
    }
    return zeroSt;
  }

  private static final long serialVersionUID = 25L;

  @Override public boolean isLocal() { return false; }
  @Override public boolean isDynamic() { return true; }
}


/**
 * "0" if first word of sentence or not first letter uppercase or if
 * lowercase version isn't in dictionary.  Otherwise first tag of lowercase
 * equivalent.
 */
class ExtractorMidSentenceCapC extends RareExtractor {

  private transient Dictionary dict;

  public ExtractorMidSentenceCapC() {
  }

  
  protected void setGlobalHolder(StanfordTagger tagger) {
    this.dict = tagger.dict;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String prevTag = pH.getTag(h, -1);
    if (prevTag == null) { return zeroSt; }
    if (prevTag.equals(naTag)) {
      return zeroSt;
    }
    String s = pH.getWord(h, 0);
    if (startsUpperCase(s)) {
      String s1 = s.toLowerCase();
      if (dict.isUnknown(s1)) {
        return zeroSt;
      }
      return dict.getFirstTag(s1);
    }
    return zeroSt;
  }

  private static final long serialVersionUID = 26L;

  @Override public boolean isLocal() { return false; }
  @Override public boolean isDynamic() { return true; }
}


class ExtractorCapC extends RareExtractor {

  private transient Dictionary dict;

  public ExtractorCapC() {
  }
  
  protected void setGlobalHolder(StanfordTagger tagger) {
    this.dict = tagger.dict;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (startsUpperCase(s)) {
      String s1 = s.toLowerCase();
      if (dict.isUnknown(s1)) {
        return zeroSt;
      }
      return dict.getFirstTag(s1);
    }
    return zeroSt;
  }

  private static final long serialVersionUID = 26L;

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }
}

// TODO: the next time we have to rebuild the tagger files anyway, we
// should change this class's name to something like
// "ExtractorNoLowercase" to distinguish it from
// ExtractorAllCapitalized
class ExtractorAllCap extends RareExtractor {

  public ExtractorAllCap() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (noneLowerCase(s)) {
      return "1";
    }
    return "0";
  }

  private static final long serialVersionUID = 27L;

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }
}


class ExtractorAllCapitalized extends RareExtractor {

  public ExtractorAllCapitalized() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (allUpperCase(s)) {
      return "1";
    }
    return "0";
  }

  private static final long serialVersionUID = 32L;

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }
}


class ExtractorCNumber extends RareExtractor {

  public ExtractorCNumber() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (containsNumber(s)) {
      return "1";
    }
    return "0";
  }

  private static final long serialVersionUID = 28L;

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }
}


class ExtractorDash extends RareExtractor {

  public ExtractorDash() {
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (containsDash(s)) {
      return "1";
    }
    return "0";
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

  private static final long serialVersionUID = 29L;
}


class ExtractorCWordSuff extends RareExtractor {

  private final int num;

  ExtractorCWordSuff(int num) {
    this.num = num;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    // String word = TestSentence.toNice(pH.getWord(h, 0));
    String word = pH.getWord(h, 0);
    if (word.length() < num) {
      return "######";
    }
    return word.substring(word.length() - num);
  }

  private static final long serialVersionUID = 30L;

  @Override
  public String toString() {
    return super.toString() + " size " + num;
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

}


class ExtractorCWordPref extends RareExtractor {

  private final int num;

  ExtractorCWordPref(int num) {
    this.num = num;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    // String word = TestSentence.toNice(pH.getWord(h, 0));
    String word = pH.getWord(h, 0);
    if (word.length() < num) {
      return "######";
    } else {
      return word.substring(0, num);
    }
  }

  private static final long serialVersionUID = 31L;

  @Override
  public String toString() {
    return super.toString() + " size " + num;
  }

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

} // end class ExtractorCWordPref


class ExtractorsConjunction extends RareExtractor {

  private Extractor extractor1;
  private Extractor extractor2;

  volatile boolean isLocal, isDynamic;

  ExtractorsConjunction(Extractor e1, Extractor e2) {
    extractor1 = e1;
    extractor2 = e2;
    isLocal = e1.isLocal() && e2.isLocal();
    isDynamic = e1.isDynamic() || e2.isDynamic();
  }

  
  protected void setGlobalHolder(StanfordTagger tagger) {
    extractor1.setGlobalHolder(tagger);
    extractor2.setGlobalHolder(tagger);
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String ex1 = extractor1.extract(h, pH);
    if (ex1.equals(zeroSt)) {
      return zeroSt;
    }
    String ex2 = extractor2.extract(h, pH);
    if (ex2.equals(zeroSt)) {
      return zeroSt;
    }
    return ex1 + ':' + ex2;
  }

  private static final long serialVersionUID = 36L;

  @Override public boolean isLocal() { return isLocal; }
  @Override public boolean isDynamic() { return isDynamic; }
}


class PluralAcronymDetector extends RareExtractor {

  public PluralAcronymDetector() {
  }

  private static boolean pluralAcronym(String s) {
    int len = s.length();
    len--;
    if (s.charAt(len) != 's') {
      return false;
    }
    for (int i = 0; i < len; i++) {
      if (!Character.isUpperCase(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s = pH.getWord(h, 0);
    if (pluralAcronym(s)) {
      return "1";
    }
    return "0";
  }

  private static final long serialVersionUID = 33L;

  @Override public boolean isLocal() { return true; }
  @Override public boolean isDynamic() { return false; }

}


//class CtbPreDetector extends RareExtractor {
//
//  private String t1;
//
//  CtbPreDetector(String t2, int n2) {
//    super(n2);
//    t1=t2;
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    String s = TestSentence.toNice(pH.getWord(h, position));
//
//    if (!s.equals("") && CtbDict.getTagPre(t1, s.substring(0, 1)).equals("1"))
//      return "1:"+t1;
//    return "0:"+t1;
//  }
//
//  private static final long serialVersionUID = 43L;
//
//  @Override
//  public String toString() {
//    return super.toString() + " tag=" + t1;
//  }
//
//  @Override public boolean isLocal() { return false; }
//  @Override public boolean isDynamic() { return false; }
//
//} // end class ctbPreDetector


//class CtbSufDetector extends RareExtractor {
//
//  private String t1;
//
//  CtbSufDetector(String t2, int n2) {
//    super(n2);
//    t1=t2;
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    String s=TestSentence.toNice(pH.getWord(h, position));
//
//    if(!s.equals("") && CtbDict.getTagSuf(t1, s.substring(s.length()-1, s.length())).equals("1"))
//      return "1:"+t1;
//    return "0:"+t1;
//  }
//
//  private static final long serialVersionUID = 44L;
//
//  @Override public boolean isLocal() { return false; }
//  @Override public boolean isDynamic() { return false; }
//
//  @Override
//  public String toString() {
//    return super.toString() + " tag=" + t1;
//  }
//
//} // end class ctbPreDetector

/*
class ASBCPreDetector extends RareExtractor {
  private String t1;
  private int n1;
  public ASBCPreDetector(String t2, int n2) {
    t1=t2;
    n1=n2;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s=TestSentence.toNice(pH.get(h,n1,false));

    if(!s.equals("") && ASBCDict.getTagPre(t1, s.substring(0, 1)).equals("1"))
      return "1:"+t1;
    return "0:"+t1;
  }
  private static final long serialVersionUID = 53L;
} // end class ASBCPreDetector

class ASBCSufDetector extends RareExtractor {
  private String t1;
  private int n1;
  public ASBCSufDetector(String t2, int n2) {
    t1=t2;
    n1=n2;
  }

  @Override
  String extract(History h, PairsHolder pH) {
    String s=TestSentence.toNice(pH.get(h,n1,false));
    if (!s.equals("") && ASBCDict.getTagSuf(t1, s.substring(s.length()-1, s.length())).equals("1"))
      return "1:"+t1;
    return "0:"+t1;
  }
  private static final long serialVersionUID = 54L;
} // end class ASBCPreDetector
*/

//class ASBCunkDetector extends RareExtractor {
//
//  private String t1;
//  private int n1;
//
//  ASBCunkDetector(String t2, int n2) {
//    t1=t2;
//    n1=n2;
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    String s=TestSentence.toNice(pH.getWord(h,n1));
//
//    if (ASBCunkDict.getTag(t1, s).equals("1"))
//      return "1:"+t1;
//    return "0:"+t1;
//  }
//
//  private static final long serialVersionUID = 57L;
//
//  @Override public boolean isLocal() { return false; }
//  @Override public boolean isDynamic() { return false; }
//
//} // end class ASBCunkDetector
//
//class CTBunkDictDetector extends RareExtractor {
//
//  private String t1;
//  private int n1;
//
//  CTBunkDictDetector(String t2, int n2) {
//    t1=t2;
//    n1=n2;
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    String s=TestSentence.toNice(pH.getWord(h,n1));
//
//    if (CTBunkDict.getTag(t1, s).equals("1"))
//      return "1:"+t1;
//    return "0:"+t1;
//  }
//
//  private static final long serialVersionUID = 80L;
//
//  @Override public boolean isLocal() { return false; }
//  @Override public boolean isDynamic() { return false; }
//
//} // end class CTBunkDictDetector
//
//class ExtractorWordShapeClassifier extends Extractor {
//
//  private final int wordShaper;
//
//  // This cache speeds things up a little bit.  I used
//  // -Xrunhprof:cpu=samples,interval=1 when using the "distsim" tagger
//  // on the training set to measure roughly how much time was spent in
//  // this method.  I concluded that with the cache, 1.24% of the time
//  // is spent here, and without the cache, 1.26% of the time is spent
//  // here.  This is a very small savings, which would be even smaller
//  // if we make the cache thread safe.  It turns out that, as written,
//  // the cache is not thread safe for various reasons.  In particular,
//  // it assumes only one wordshape classifier is ever used, which
//  // might not be true even with just one tagger, and has an even
//  // higher chance of not being true if there are multiple taggers.
//  // Furthermore, access to the cache should really be synchronized
//  // regardless.  The easiest solution is to comment out the cache and
//  // note that if you want to bring it back, make it a map from wsc to
//  // cache rather than just a single cache.  -- horatio
//  //private static final Map<String, String> shapes =
//  //  new HashMap<String, String>();
//  // --- should be:
//  //private static final Map<String, Map<String, String>> ...
//
//  ExtractorWordShapeClassifier(int position, String wsc) {
//    super(position, false);
//    wordShaper = WordShapeClassifier.lookupShaper(wsc);
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    String s = super.extract(h, pH);
//    String shape = WordShapeClassifier.wordShape(s, wordShaper);
//    return shape;
//  }
//
//  private static final long serialVersionUID = 101L;
//
//  @Override public boolean isLocal() { return position == 0; }
//  @Override public boolean isDynamic() { return false; }
//}
//
//
///**
// * This extractor extracts a conjunction of word shapes.
// */
//class ExtractorWordShapeConjunction extends Extractor {
//
//  private static final long serialVersionUID = -49L;
//
//  private final int wordShaper;
//  private final int left;
//  private final int right;
//  private final String name;
//
//  ExtractorWordShapeConjunction(int left, int right, String wsc) {
//    super();
//    this.left = left;
//    this.right = right;
//    wordShaper = WordShapeClassifier.lookupShaper(wsc);
//    name = "ExtractorWordShapeConjunction(" + left + ',' + right + ')';
//  }
//
//  @Override
//  String extract(History h, PairsHolder pH) {
//    StringBuilder sb = new StringBuilder();
//    for (int j = left; j <= right; j++) {
//      String s = pH.getWord(h, j);
//      sb.append(WordShapeClassifier.wordShape(s, wordShaper));
//      if (j < right) {
//        sb.append('|');
//      }
//    }
//    return sb.toString();
//  }
//
//  @Override
//  public String toString() {
//    return name;
//  }
//
//  @Override public boolean isLocal() { return false; }
//  @Override public boolean isDynamic() { return false; }
